
import validateHelper, { expectNoErrorsOrWarnings } from '../validate-helper.js';

describe('validation plugin - semantic - oas3 refs', () => {
  describe('$refs for request bodies must reference a request body by position', () => {
    it(
      'should return an error when a requestBody incorrectly references a local component schema',
      () => {
        const spec = {
          openapi: '3.0.0',
          paths: {
            '/': {
              post: {
                operationId: 'myId',
                requestBody: {
                  $ref: '#/components/schemas/MySchema'
                }
              }
            }
          },
          components: {
            schemas: {
              MySchema: {
                type: 'string'
              }
            }
          }
        };

        return validateHelper(spec)
          .then(system => {
            const allErrors = system.errSelectors.allErrors().toJS();
            const firstError = allErrors[0];
            expect(allErrors.length).toEqual(1);
            expect(firstError.message).toEqual('requestBody $refs cannot point to \'#/components/schemas/…\', they must point to \'#/components/requestBodies/…\'');
            expect(firstError.path).toEqual(['paths', '/', 'post', 'requestBody', '$ref']);
          });
      }
    );
    it(
      'should not return an error when a requestBody references a remote component schema',
      () => {
        const spec = {
          openapi: '3.0.0',
          paths: {
            '/': {
              post: {
                operationId: 'myId',
                requestBody: {
                  $ref: 'http://google.com/#/components/schemas/MySchema'
                }
              }
            }
          }
        };

        return expectNoErrorsOrWarnings(spec);
      }
    );
    it(
      'should return an error when a requestBody in a callback incorrectly references a local component schema',
      () => {
        const spec = {
          openapi: '3.0.0',
          info: null,
          version: '1.0.0-oas3',
          title: 'example',
          paths: {
            '/api/callbacks': {
              post: {
                responses: {
                  '200': {
                    description: 'OK'
                  }
                },
                callbacks: {
                  callback: {
                    '/callback': {
                      post: {
                        requestBody: {
                          $ref: '#/components/schemas/callbackRequest'
                        },
                        responses: {
                          '200': {
                            description: 'OK'
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          components: {
            schemas: {
              callbackRequest: {
                type: 'object',
                properties: {
                  property1: {
                    type: 'integer',
                    example: 10000
                  }
                }
              }
            }
          }
        };

        return validateHelper(spec)
          .then(system => {
            const allErrors = system.errSelectors.allErrors().toJS();
            const firstError = allErrors[0];
            expect(allErrors.length).toEqual(1);
            expect(firstError.message).toEqual('requestBody $refs cannot point to \'#/components/schemas/…\', they must point to \'#/components/requestBodies/…\'');
            expect(firstError.path).toEqual(['paths', '/api/callbacks', 'post', 'callbacks',
            'callback', '/callback', 'post', 'requestBody', '$ref']);
          });
      }
    );
    it(
      'should return no errors when a requestBody correctly references a local component request body',
      () => {
        const spec = {
          openapi: '3.0.0',
          paths: {
            '/': {
              post: {
                operationId: 'myId',
                requestBody: {
                  $ref: '#/components/requestBodies/MyBody'
                }
              }
            }
          },
          components: {
            requestBodies: {
              MyBody: {
                content: {
                  'application/json': {
                    schema: {
                      type: 'string'
                    }
                  }
                }
              }
            }
          }
        };

        return expectNoErrorsOrWarnings(spec);
      }
    );
    it(
      'should return no errors when a requestBody correctly references a local operation request body',
      () => {
        const spec = {
          openapi: '3.0.0',
          paths: {
            '/': {
              post: {
                operationId: 'myId',
                requestBody: {
                  $ref: '#/paths/~1/put/requestBody'
                }
              },
              put: {
                requestBody: {
                  content: {
                    'application/json': {
                      schema: {
                        type: 'string'
                      }
                    }
                  }
                }
              }
            }
          }
        };

        return expectNoErrorsOrWarnings(spec);
      }
    );
    it(
      'should return no errors when a requestBody correctly references a remote component request body',
      () => {
        const spec = {
          openapi: '3.0.0',
          paths: {
            '/': {
              post: {
                operationId: 'myId',
                requestBody: {
                  $ref: 'http://google.com/#/components/requestBodies/MyBody'
                }
              }
            }
          },
          components: {
            requestBodies: {
              MyBody: {
                content: {
                  'application/json': {
                    schema: {
                      type: 'string'
                    }
                  }
                }
              }
            }
          }
        };

        return expectNoErrorsOrWarnings(spec);
      }
    );
    it(
      'should return no errors when a requestBody correctly references a remote https component request body',
      () => {
        const spec = {
          openapi: '3.0.0',
          paths: {
            '/': {
              post: {
                operationId: 'myId',
                requestBody: {
                  $ref: 'https://example.com/file.yaml#/components/requestBodies/group1/addPetBody'
                }
              }
            }
          },
          components: {
            requestBodies: {
              MyBody: {
                content: {
                  'application/json': {
                    schema: {
                      type: 'string'
                    }
                  }
                }
              }
            }
          }
        };

        return expectNoErrorsOrWarnings(spec);
      }
    );
    it(
      'should return no errors when a requestBody correctly references an external yaml file',
      () => {
        const spec = {
          openapi: '3.0.0',
          paths: {
            '/': {
              post: {
                operationId: 'myId',
                requestBody: {
                  $ref: 'addPetBody.yaml'
                }
              }
            }
          },
          components: {
            requestBodies: {
              MyBody: {
                content: {
                  'application/json': {
                    schema: {
                      type: 'string'
                    }
                  }
                }
              }
            }
          }
        };

        return expectNoErrorsOrWarnings(spec);
      }
    );
    it(
      'should return no errors when a requestBody correctly references an external yaml pointing some node',
      () => {
        const spec = {
          openapi: '3.0.0',
          paths: {
            '/': {
              post: {
                operationId: 'myId',
                requestBody: {
                  $ref: './components.yaml#/path/to/some/node'
                }
              }
            }
          },
          components: {
            requestBodies: {
              MyBody: {
                content: {
                  'application/json': {
                    schema: {
                      type: 'string'
                    }
                  }
                }
              }
            }
          }
        };

        return expectNoErrorsOrWarnings(spec);
      }
    );
  });

  describe('$refs for requestbody schemas must reference a schema by position', () => {
    it(
      'should return an error when a requestBody schema incorrectly references a local component requestBody',
      () => {
        const spec = {
          openapi: '3.0.0',
          paths: {
            '/foo': {
              post: {
                requestBody: {
                  content: {
                    'application/json': {
                      schema: {
                        $ref: '#/components/requestBodies/Foo'
                      }
                    }
                  }
                }
              }
            }
          },
          components: {
            requestBodies: {
              Foo: {
                type: 'string'
              }
            }
          }
        };

        return validateHelper(spec)
          .then(system => {
            const allErrors = system.errSelectors.allErrors().toJS();
            const allSemanticErrors = allErrors.filter(err => err.source === 'semantic');
            
            expect(allSemanticErrors.length).toEqual(1);
            
            const firstError = allSemanticErrors[0];
            
            expect(firstError.message).toEqual('requestBody schema $refs must point to a position where a Schema Object can be legally placed');
            expect(firstError.path).toEqual(['paths', '/foo', 'post', 'requestBody', 'content', 'application/json', 'schema', '$ref']);

          });
      }
    );

    it(
      'should not return an error when a requestBody schema references a local component schema',
      () => {
        const spec = {
          openapi: '3.0.0',
          paths: {
            '/foo': {
              post: {
                requestBody: {
                  content: {
                    'application/json': {
                      schema: {
                        $ref: '#/components/schemas/Foo'
                      }
                    }
                  }
                }
              }
            }
          },
          components: {
            schemas: {
              Foo: {
                type: 'string'
              }
            }
          }
        };

        return validateHelper(spec)
          .then(system => {
            const allErrors = system.errSelectors.allErrors().toJS();
            expect(allErrors.length).toEqual(0);
          });
      }
    );

    it(
      'should not return an error when a requestBody schema references remote document paths',
      () => {
        const spec = {
          openapi: '3.0.0',
          paths: {
            '/foo': {
              post: {
                requestBody: {
                  content: {
                    'application/json': {
                      schema: {
                        $ref: 'http://google.com#/Foo'
                      }
                    }
                  }
                }
              }
            }
          }
        };

        return validateHelper(spec)
          .then(system => {
            const allErrors = system.errSelectors.allErrors().toJS();
            expect(allErrors.length).toEqual(0);
          });
      }
    );

    it(
      'should not return an error when a requestBody schema references entire remote documents',
      () => {
        const spec = {
          openapi: '3.0.0',
          paths: {
            '/foo': {
              post: {
                requestBody: {
                  content: {
                    'application/json': {
                      schema: {
                        $ref: 'addPetBody.yaml'
                      }
                    }
                  }
                }
              }
            }
          }
        };

        return validateHelper(spec)
          .then(system => {
            const allErrors = system.errSelectors.allErrors().toJS();
            expect(allErrors.length).toEqual(0);
          });
      }
    );

    it(
      'should not return an error when a requestBody schema references local operation requestBody schemas',
      () => {
        const spec = {
          openapi: '3.0.0',
          paths: {
            '/foo': {
              post: {
                responses: {
                  '200': {
                    description: 'OK'
                  }
                },
                requestBody: {
                  content: {
                    'application/json': {
                      schema: {
                        type: 'string'
                      }
                    }
                  }
                }
              },
              put: {
                requestBody: {
                  responses: {
                    '200': {
                      description: 'OK'
                    }
                  },
                  content: {
                    'application/json': {
                      schema: {
                        $ref: '#/paths/~1foo/post/requestBody/content/application~1json/schema'
                      }
                    }
                  }
                }
              }
            }
          }
        };

        return validateHelper(spec)
          .then(system => {
            const allErrors = system.errSelectors.allErrors().toJS();
            expect(allErrors.length).toEqual(0);
          });
      }
    );
  });

  describe('response header $refs should not point to parameters', () => {
    it(
      'should return an error when a response header incorrectly references a local parameter component',
      () => {
        const spec = {
          openapi: '3.0.0',
          paths: {
            '/foo': {
              get: {
                responses: {
                  '200': {
                    description: 'OK',
                    headers: {
                      'X-MyHeader': {
                        $ref: '#/components/parameters/MyHeader'
                      }
                    }

                  }
                }
              }
            }
          },
          components: {
            headers: {
              MyHeader: {
                $ref: '#/components/parameters/MyHeader'
              }
            },
            parameters: {
              MyHeader: {}
            }
          }
        };

        return validateHelper(spec)
          .then(system => {
            const allErrors = system.errSelectors.allErrors().toJS();
            const firstError = allErrors[0];
            expect(allErrors.length).toEqual(1);
            expect(firstError.message).toEqual('OAS3 header $refs should point to #/components/headers/... and not #/components/parameters/...');
            expect(firstError.path).toEqual(['paths', '/foo', 'get','responses','200', 'headers', 'X-MyHeader', '$ref']);
          });
      }
    );
    
    it(
      'should return no errors when a response header correctly references a local header component',
      () => {
        const spec = {
          openapi: '3.0.0',
          paths: {
            '/foo': {
              get: {
                responses: {
                  '200': {
                    description: 'OK',
                    headers: {
                      'X-MyHeader': {
                        $ref: '#/components/headers/MyHeader'
                      }
                    }

                  }
                }
              }
            }
          },
          components: {
            headers: {
              MyHeader: {
                $ref: '#/components/headers/MyHeader'
              }
            }
          }
        };

        return expectNoErrorsOrWarnings(spec);
      }
    );
    
    it('should return no errors for external $refs in response headers', () => {
      const spec = {
        openapi: '3.0.0',
        paths: {
          '/foo': {
            get: {
              responses: {
                '200': {
                  description: 'OK',
                  headers: {
                    'X-MyHeader': {
                      $ref: 'https://www.google.com/#/components/parameter/MyHeader'
                    }
                  }

                }
              }
            }
          }
        },
        components: {
          headers: {
            MyHeader: {
              $ref: '#/components/headers/MyHeader'
            }
          }
        }
      };

      return expectNoErrorsOrWarnings(spec);
    });
  });

  describe('parameter $refs should not point to header components', () => {
    it(
      'should return an error when a parameter incorrectly references a response header component',
      () => {
        const spec = {
          openapi: '3.0.0',
          paths: {
            '/foo': {
              parameters: [
                {
                  $ref: '#/components/headers/foo'
                }
              ],
              get: {
                parameters: [
                  {
                    $ref: '#/components/headers/foo'
                  }
                ],
                responses: {
                  '200': {
                    description: 'OK'
                  }
                }
              }
            }
          },
          components: {
            parameters: {
              myParam: {
                $ref: '#/components/headers/foo'
              }
            },
            headers: {
              foo: {}
            }
          }
        };

        return validateHelper(spec)
          .then(system => {
            const allErrors = system.errSelectors.allErrors().toJS();
            expect(allErrors.length).toEqual(3);
            const firstError = allErrors[0];
            expect(firstError.message).toEqual('OAS3 parameter $refs should point to #/components/parameters/... and not #/components/headers/...');
            expect(firstError.path).toEqual(['paths','/foo','parameters', '0', '$ref']);
            const secondError = allErrors[1];
            expect(secondError.message).toEqual('OAS3 parameter $refs should point to #/components/parameters/... and not #/components/headers/...');
            expect(secondError.path).toEqual(['paths','/foo','get','parameters', '0', '$ref']);
            const thirdError = allErrors[2];
            expect(thirdError.message).toEqual('OAS3 parameter $refs should point to #/components/parameters/... and not #/components/headers/...');
            expect(thirdError.path).toEqual(['components','parameters', 'myParam', '$ref']);
          });
      }
    );
    
    it(
      'should return no errors when a parameter correctly references a parameter component',
      () => {
        const spec = {
          openapi: '3.0.0',
          paths: {
            '/foo': {
              parameters: [
                {
                  $ref: '#/components/parameters/foo'
                }
              ],
              get: {
                 parameters: [
                {
                  $ref: '#/components/parameters/foo'
                }
              ],
                responses: {
                  '200': {
                    description: 'OK'
                  }
                }
              }
            }
          },
          components: {
            parameters: {
              foo: {
                $ref: '#/components/parameters/foo'
              }
            }
          }
        };

        return expectNoErrorsOrWarnings(spec);
      }
    );
    
    it('should return no errors for external parameter $refs', () => {
      const spec = {
        openapi: '3.0.0',
        paths: {
          '/foo': {
            parameters: [
              {
                $ref: 'http://www.google.com/#/components/parameters/foo'
              }
            ],
            get: {
              parameters: [
                {
                  $ref: 'http://www.google.com/#/components/parameters/foo'
                }
              ],
              responses: {
                '200': {
                  description: 'OK'
                }
              }
            }
          }
        },
        components: {
          parameters: {
            foo: {
              $ref: 'http://www.google.com/#/components/parameters/foo'
            }
          }
        }
      };

      return expectNoErrorsOrWarnings(spec);
    });
  });
});